# -*- coding: utf-8 -*-
# This code written by ChanGOGOGO
import datetime
import hashlib
import json
import logging
import os
import random
import string
import time
import pymysql
import requests
import yaml
import base64
from logging.handlers import RotatingFileHandler
from functools import wraps
from faker import Faker
from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives import padding
from typing import Union


def calc_excute_time(func):
    """计算func执行时间"""

    @wraps(func)
    def decorated(*args, **kwargs):
        start_time = time.time()
        result = func(*args, **kwargs)
        end_time = time.time()
        consumer_time = end_time - start_time
        logging.info(f"{func.__name__}执行共计耗时{consumer_time:.2f}秒。")
        return result

    return decorated


def data2beautify(data: Union[dict, list]) -> str:
    """
    字典或列表类型的数据，进行美化输出。

    :param data: 字典或列表类型数据
    :return: 格式化后的字符串数据
    """
    indent = 4

    if not isinstance(data, (dict, list)):
        raise TypeError("输入数据必须是字典或列表类型")
    try:
        formatted_data = json.dumps(data, indent=indent, ensure_ascii=False)
        return formatted_data
    except (TypeError, ValueError) as e:
        raise ValueError(f"无法序列化输入数据: {e}")


def setup_logging(log_level: str = "INFO", write_log: bool = False):
    """
    日志格式化打印
    :param log_level: 打印高于等于设定级别的日志。DEBUG < INFO < WARNING < ERROR < CRITICAL
    :param write_log: 是否需要将日志写入到文件
    :return: None
    """
    # 创建日志器
    logger = logging.getLogger()
    logger.setLevel(log_level)  # 设置日志级别为DEBUG
    # 移除所有已经存在的处理器
    for handler in logger.handlers[:]:
        logger.removeHandler(handler)
    # 设置日志格式
    formatter = logging.Formatter("%(asctime)s [%(levelname)s]:%(message)s")
    # 创建一个流处理器，输出到控制台
    console_handler = logging.StreamHandler()
    console_handler.setFormatter(formatter)
    logger.addHandler(console_handler)
    # 根据参数决定是否也将日志写入文件
    if write_log:
        # 确保日志目录存在
        if not os.path.exists("log"):
            os.makedirs("log")
        # 创建一个循环文件处理器，自动根据大小切割
        file_handler = RotatingFileHandler(
            "log/info.log", maxBytes=1048576, backupCount=5
        )
        file_handler.setFormatter(formatter)
        logger.addHandler(file_handler)


setup_logging()


@calc_excute_time
def post_json(url: str, data: dict | list, log_switch: bool = True, **kwargs) -> dict:
    """
    发送post请求，请求内容为json数据
    :param url: 请求url
    :param data: 请求体
    :param log_switch: 日志开关
    :return: 接口响应
    """
    headers = kwargs.get("headers")
    if headers:
        ...
    else:
        headers = {"Content-Type": "application/json"}
    if log_switch:
        logging.info(
            f"[请求消息]向{url}接口发起POST请求。\n请求头：\n{data2beautify(headers)}\n请求体：\n{data2beautify(data)}"
        )
    res = requests.post(url, data=json.dumps(data), headers=headers, timeout=300).json()
    if log_switch:
        logging.info(f"[响应消息]从{url}接口收到响应：\n{data2beautify(res)}")
    return res


str_range = list(string.ascii_letters)


def random_str(str_len=32) -> str:
    """生成指定长度的随机字符串"""
    random_str_list = []
    for x in range(str_len):
        current_str = random.choice(str_range)
        random_str_list.append(current_str)
    result = "".join(random_str_list)
    return result


faker_object = Faker("zh_CN")


def random_phone():
    """
    随机手机号(不重复)
    :return: 手机号
    """
    phone = faker_object.unique.phone_number()
    return phone


def operate_mysql(
    sql_sentence,
    host,
    db_name,
    port: int = 3306,
    user: str = "root",
    password: str = "uWXf87plmQGz8zMM",
):
    """
    数据库操作，仅使用于单次操作，需要循环执行sql语句的话需要进行改造
    :param sql_sentence: SQL语句
    :param host: 数据库IP
    :param db_name: 库名
    :param port: 数据库端口
    :param user: 用户名
    :param password: 密码
    :return: sql执行结果
    """
    conn = pymysql.connect(
        host=host, port=port, user=user, passwd=password, db=db_name, charset="utf8"
    )
    cursor = conn.cursor()
    cursor.execute(sql_sentence)
    data = cursor.fetchall()
    cursor.close()
    conn.close()
    return data


def calc_time_interval(start_time: str, end_time: str) -> int:
    """
    计算两个时间之间的间隔时长
    :param start_time: 开始时间，格式YYYY-mm-dd HH:MM:SS
    :param end_time: 结束时间，格式YYYY-mm-dd HH:MM:SS
    :return: 间隔时长(秒)
    """
    start = datetime.datetime.strptime(start_time, "%Y-%m-%d %H:%M:%S")
    end = datetime.datetime.strptime(end_time, "%Y-%m-%d %H:%M:%S")
    return int((end - start).total_seconds())


def random_date_time_between(start_date: str = "-2d", end_date: str = "now"):
    """"""
    finish_time = faker_object.date_time_between(
        start_date=start_date, end_date=end_date, tzinfo=None
    )
    return finish_time


def get_now_time() -> str:
    """
    获取当前时间，格式为YYYY-mm-dd HH:MM:SS
    :return: 当前时间
    """
    now_time = time.strftime("%Y-%m-%d %H:%M:%S")
    return now_time


def get_yyyymmddhhmmss() -> str:
    """
    获取当前时间，格式为YYYYmmddHHMMSS
    :return: 当前时间
    """
    now_time = time.strftime("%Y%m%d%H%M%S")
    return now_time


def get_yyyymmddhhmmssmmm() -> str:
    """
    获取当前时间，格式为YYYYmmddHHMMSSmmm
    :return: 当前时间（精确到毫秒）
    """
    now = datetime.datetime.now()
    return now.strftime("%Y%m%d%H%M%S%f")[:-3]


def extract_element_by_proportion(
    target_list: list, extarct_scale: list, extarct_num: int = 1
) -> list:
    """
    从列表中按比例抽取指定个数的元素
    :param target_list: 需要抽取的列表对象
    :param extarct_scale: 每个元素被抽取的比例。列表对象中每个元素都需要指定一个抽取比例，所有元素的抽取比例之和必须等于1
    :param extarct_num: 抽取的元素个数
    :return: 被抽取元素组成的列表
    """
    element = random.choices(target_list, extarct_scale, k=extarct_num)
    return element


def read_yaml(config_path=r"./suning_config/client_config.yaml"):
    """读取yaml文件"""
    with open(config_path, encoding="utf-8") as f:
        config = yaml.safe_load(f)
    return config


def encrypy_md5(str_object: str, md5_len: int = 32, upper: bool = False):
    """字符串进行MD5加密"""
    # 创建md5对象
    md5_object = hashlib.md5()
    # 对字符串进行编码
    str_encode = str_object.encode(encoding="utf-8")
    md5_object.update(str_encode)
    encrypy_object = md5_object.hexdigest()
    if md5_len == 16:
        encrypy_object = encrypy_object[8:-8]
    elif md5_len == 32:
        ...
    else:
        raise ValueError("参数md5_len只能是16或者32")
    if upper:
        encrypy_object = encrypy_object.upper()
    return encrypy_object


def encrypt_aes(plaintext, key="icekredit2hgzy00"):
    """AES加密，使用ECB/PKCS5Padding模式"""
    key_bytes = key.encode("utf-8")[:16]
    cipher = Cipher(algorithms.AES(key_bytes), modes.ECB(), backend=default_backend())
    encryptor = cipher.encryptor()
    padder = padding.PKCS7(algorithms.AES.block_size).padder()
    padded_data = padder.update(plaintext.encode("utf-8")) + padder.finalize()
    encrypted_bytes = encryptor.update(padded_data) + encryptor.finalize()
    return base64.b64encode(encrypted_bytes).decode("utf-8")


if __name__ == "__main__":
    print(get_yyyymmddhhmmssmmm())
